FridaFuzz总结
起始
您可以使用Notion的内置目录功能来自动生成目录。在左侧边栏中,选择“添加目录”,然后将其拖动到您想要的位置。在文档中添加标题后,目录将自动更新。注意,此功能仅在页面级别上工作,而不是在段落级别上工作。
大概12.17.18号,走模拟的方案,因为要克服的障碍太多了(各种依赖),最终在费劲修复了ExANE的一个bug之后还是选择换一个fuzz方案,
frida方案的优势和劣势
对于服务器、防火墙这种东西来说,配置本来就高,frida模式可以肆无忌惮的去fuzz
但是对于手机来说,内存就很成问题,虽然未来的趋势肯定是内存越来越高,Android系统的稳定性也是一大问题
但是要用frida-fuzz,部署环境得是支持frida_server的机器,而且还得能跑AFL,像Android部署就支持frida_server,只不过部署AFL++时要克服一下共享内存和信号量的问题。
总的来说,不需要模拟的原生态fuzz本身就减少了很大的工作量了。
安卓Native层共享库fuzzing技术思路及实践 - 掘金 (juejin.cn)
这篇21 2月份发的文章其实就已经在讨论Android fuzz的事了,现在已经两年了。。。只不过其是去fuzz的纯Native库,
这里fuzz的目标入口大概分3个等级
- 纯Native代码,不涉及Java虚拟机环境
- JNI函数,设计JNIEnv,需要一定的和Java虚拟机交互的能力
- 直接调用Java函数,需要完全的Java虚拟机Hook能力
用frida去fuzz,理论上这三个目标入口等级都是支持的,只不过从后期的实践效果来看,越往下越不稳定,但是辅助逆向工作量越小
AFLplusplus/frida_mode at stable · AFLplusplus/AFLplusplus (github.com)
AFL++也引入了fridamode,这个后面也有分析,一些资料
AFLplusplus/Scripting.md at stable · AFLplusplus/AFLplusplus (github.com)
Blackbox Fuzzing #4: New AFL++ FRIDA mode, How it Perform against QEMU mode?
Android漏洞挖掘资料
Android Security Acknowledgements | Android Open Source Project
Android的漏洞致谢列表,最好的研究资料。
研究root的资料 —- DirtyPipe在Android上的移植
https://github.com/polygraphene/DirtyPipe-Android
字节的污点分析工具
https://github.com/bytedance/appshark
WebView漏洞学习
Android security checklist: WebView | Oversecured Blog
https://github.com/idhyt/AndroidFridaFuzz
这个也非常关键,三星的漏洞列表
三星的SRC,以及漏洞认证分类
Security Reporting | Samsung Mobile Security
Frida_mode的核心是stalker,下面分析一下stalker
stalker
Stalker | Frida • A world-class dynamic instrumentation toolkit
stalker是一个动态插桩工具,允许对x86、x64、arm32、arm64进行动态插桩
Anatomy of a code tracer | by Ole André Vadla Ravnås | Medium
大胡子最初写的关于stalker的介绍
stalker源自于大胡子急需一个tracer
软件断点会修改内存,硬件断点也很容易被检测到,基于调试器去实现,每次trap都会消耗大量的资源,所以灵活粒度很关键,其思想就是把原来要执行的代码拷贝出来,然后在副本上加上各种log桩点,这样原先执行的反调试和校验逻辑可以正常执行,而且我们也可以粒度灵活
只对call感兴趣就只在call周围加,意味着可以根据是需要添加运行时开销
这个是实现起来也不简单,首先各种指令集插代码就和很麻烦,Capstone反编译器缓解了一部分问题,而且许多代码是位置相关的,需要调整
stalker的设计是一个基本块一个基本块的翻译,一个基本块执行完,就返回到引擎,计算分支信息,翻译下一个基本块去执行,这样肯定代价很大,一个优化策略就是重用已经编译的代码
另外像jmp+56这种,我们可以直接patch成直接跳转,但是这种修改必须谨慎,因为有些程序是自修改的,因此需要引入一个trust-threshold参数,
stalker的官方文档,其中很多内容暂时都用不上,因为涉及到stalker的底层实现,挺复杂的
Stalker | Frida • A world-class dynamic instrumentation toolkit
Transform
Transform是用来产生插桩代码的,默认的Transform长这个样
可以看到默认的Transform一次遍历一条指令,Keep就是指令保持原样
Transform是在gum_exec_ctx_obtain_block_for调用的,也即执行到一个基本块时调用用户定义的transform
控制流跑到Stalker引擎时会保存运行上下文,stalker会存储插桩指令在slabs里面
新版本的stalker好像已经分code_slab和data_slab,虽然官方的doc中写的还是原来的slab
这个是在14.2中提到的
Frida 正常的unfollow里面会调用gum_exec_ctx_free来释放内存
Frida框架层
在开发FridaFuzz的过程中,更多的还是去使用其框架层,这其中有几篇关于框架层介绍的文章
Frida Internal - Part 1: 架构、Gum 与 V8 - evilpan
frida-gum
对各种架构指令集进行inline-hook的基础
https://github.com/frida/frida-gum
stalker的实现也在这个项目的具体后端代码里
frida-gum/gum/backend-arm64 at main · frida/frida-gum (github.com)
不知道后面再调试代码bug是不是还要详细研究这个代码
除了stalker,基于frida-gum,frida还实现了一个内存监控模块。。。其实正好是去年给HW做开发的时候我研究的东西。甚至原理跟frida的都一样
gum-js
就说是Frida-gum很强大,但是C语言接口使用很不方便,因此有了上层frida接口的JS封装,这块还没深入研究过。
frida-core
Frida-core封装了很多系统级的东西,我们用frida-tools,其实内部都是通过python binding走的fride-core
frida-java-bridge
最最靠近我们所编写的JS的脚本的一层,发现有疑惑的API,以及奇怪的报错,可以先去这里寻找一些内部实现
frida/frida-java-bridge: Java runtime interop from Frida (github.com)
frida-server(主要是stalker的修复线)
上面说的那么多东西,特别是frida-core和frida-gum最终是以集成到frida-server中给用户呈现使用的。
当时想起来研究frida-server,是因为在测试fuzzer的过程中,总是稳定的在iter几次之后崩溃,错误如下
这个错我一开始以为是用Java.perform才遇到的,后来改写成JNI的native调用模式,还是稳定崩溃,于是就开始长达好几天的debug,最后没办法了,才研究的frida-server的debug编译,尝试从源码中找到bug
整个bug的唯一线索就是图片中给出的bug发生的pc值,如果从adb logcat中看的话,可以看到bug发生在frida-agent中,当然也可以从fpicker前面打印的模块地址列表中,找到这个地址在哪个模块里
编译
[原创][分享]fridaserver去特征检测以及编译-Android安全-看雪论坛-安全社区|安全招聘|bbs.pediy.com (kanxue.com)
看雪上编译frida-server的文档还挺多的,因为逆向大佬通常需要魔改server来绕过检测
先把frida拉下来
git clone **--**recurse**-**submodules https:**//**github.com**/**frida**/**frida
配置NDK路径
export ANDROID_NDK_ROOT**=**'/opt/android-ndk-r22b'
不过现在需要的25了
直接运行make core-android-arm64就行
如果要编译debug版本带调试符号的server可以
Creating debug build · Issue #1107 · frida/frida (github.com)
Before building Frida I start by editing
config.mk
to remove--strip
, so the resulting binaries have debug symbols.
编译成功的效果
有了调试符号,下面就介绍一下我是怎么fix这个bug的
stalker的内存bug fix
通过调试符号追溯那张图中PC对应的位置
这个错发生在 gum_exec_block_maybe_create_new_data_slab
具体位置在这里,那就是这里分配内存失败了啊
有时候也会发生在code_slab的分配过程中
简单调用栈如下,(应该是改成Java.perform主动调用上层Java函数才能得到这里的native调用栈,如果主动调用的是native函数就不会得到这个调用栈)
gum_exec_ctx_switch_block
gum_exec_ctx_obtain_block_for
gum_exec_block_new
gum_exec_block_maybe_create_new_code_slabs
因为我们没办法调试frida-server,就只能插桩打印,找了几个打印都不行,最后直接用的_android_log_print打印到logcat里面
在new_code_slab和allocate_near中加上相关地址打印
new_code_slab
1 | static GumCodeSlab * |
allocate_near
1 | gpointer |
分配空间先会从gum_memory_allocate 中分配,里面就是一个mmap,如果分配条件为空或者gum_address_spec_is_satisfied_by 不满足,就会调用gum_enumerate_free_ranges遍历内存 ,从当前空闲的内存区域中分配,这里spec=NULL,就是我做的fix,后面再说,
gum_address_spec_is_satisfied_by 是判断当前分配的地址是否离spec过远
本来alloc_near会传入一个建议分配到的地址,但是mmap要求分配的地址和返回地址不一定一样,这里我做了个实验,可以看到我传的预想地址是0x7000.结果两次mmap返回的地址都不是
既然这里mmap addr和mmap result地址不同,那如果是这样,总有可能分配出来的大于uint32
之所以报crash,就是因为申请到的地址大于0x7ffffffff不满足条件,所以触发了下面alloc in range,结果一个都找不到,最终只能返回0,因为slab初始化时因为访问了0地址,产生了各种奇怪的0x18、0x10类的错误,其实都是结构体成员的偏移。我们用stalker插桩还必须得让其能够分配出来slab,所以这里到底为什么要加这个spec,原因不是很明白。。分配不出来怎么办,大胡子也没说,所以最终我尝试始终给spec赋值为NULL,这样其就不会检查分配出的内存的合理性,相当于取消spec检查,结果发现反而可以正常跑了。。。
老版本是没有这个spec的限制的
直到14.2.14引入的code和data slab分离,spec也是在这时被加入的
当时就没搞懂为什么要加这个限制,看文档疑似讨论了这个点,还是不懂
最后只能在github上提了个issue,到现在也没人回
https://github.com/frida/frida-gum/issues/707
AFL++
本文主要使用的fpicker-android中引用AFL++版本,其Readme中介绍的是用AOSP的编译环境进行编译的,最新版本的AFL++,我还没有试过用AOSP的环境能不能编译出来,其对应的编译文件Android.bp的开头就写了因为AFL++没有团队成员用Android。。所以大概率编译会出错,不过暂时不用考虑这个问题,AFL++的变异驱动,先用fpicker-android这个版本,能用就行。
fpicker使用的版本还是3.13
编译
要编译AFL++,得先配置AOSP的编译环境,这个已经不知道复现多少次了
2020年安卓源码编译指南及FART脱壳机谷歌全设备镜像发布-安全客 - 安全资讯平台 (anquanke.com)
AOSP配置
原先都是刷Android8,直接下载别人下载好的源码包,现在要fuzz的是最新的12、13,只能自己把整个源码包下载下来了,还好国内有科大、清华两个源
下载源代码 | Android 开源项目 | Android Open Source Project
下载完毕后
repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest -b android-12.1.0_r1
这中间可能会经历git的错误
这个问题修复,其让我增大http的缓存,但实际上他给的buffer大小也就500MB,jdk11我下载发现是578MB,所以我们再扩大一点buffer
git config –global http.postBuffer 10485760000
如果还报错,可以把错误的对象分支删掉。。要是还有别的错误可以问下chatGPT
(46条消息) fatal: bad object refs/remotes/origin/xxx 解决方法_舜岳的博客-CSDN博客
afl++编译
AFL++的编译非常简单,只需要把fpicker里跟的AFL++拖到AOSP的根目录
先运行source build/envsetup配置下环境
然后lunch选第二项(arm64-eng)
最后直接mmm afl++即可,编译成功会给出afl++产生在哪个文件夹里
这里编译用的应该就是Android.bp,Android的编译系统
Soong Build System | Android Open Source Project
但是没看出bp哪里有特别的地方,
编译成功的效果
FASAN,常规的Address Sanitizer basics是用DT_NEED把 ASAN的库链接到目标程序里,主要逻辑包括设置shadow memory,提供内存替代函数的实现。
主要的替换就是malloc、free、memcpy等
testcase
一开始疑惑frida-mode怎么测,后来发现frida-mode里面有示例的testcase
AFLplusplus/GNUmakefile at stable · AFLplusplus/AFLplusplus (github.com)
以一个freetype的MakeFile看一下frida_mode是怎么fuzz的
下载freetype
libfuzzer的Harness
就是包裹一层main函数
源码分析
2-11
参数解析
i 设置输入目录,指定-i- 就是恢复模式
1 | case 'i': /* input dir */ |
Resume模式
搜索_resume
处理resume的逻辑在这里
就是简单把原来的queue重命名为_resume
1 |
|
in_place_resume就是输入参数-in-时设置的
而Handle_exiting_out_dir是在配置输出目录时设置的
1 | void setup_dirs_fds(afl_state_t *afl) { |
然后看一下queue目录是怎么生成的
fuzz刚开始时候的输出
首先是scan,然后是load,然后是create hard links,就在pivot_inputs里
Pivot-queue实际就是遍历queue_paths,然后在queue中创建对应项
1 | /* Create hard links for input test cases in the output directory, choosing |
创建hard_link的函数就是Link_or_copy ,就是拷贝
1 | static void link_or_copy(u8 *old_path, u8 *new_path) { |
正常创建queue,在开局scan input_dir时会创建一次,
1 | /* Read all testcases from the input directory, then queue them for testing. |
之后就是save_if_intersting ,会把感兴趣的样本也放进去
1 | /* Check if the result of an execve() during routine fuzzing is interesting, |
共享内存的创建
AFL通过共享内存来和Forkserver共享覆盖率
1 |
|
map-size就是65536,shmget打开共享内存,如果开了CMP_LOG,下面还会打开一个cmplog_shm_id
这个id会被设置到环境变量中,最后会通过shmat将两个id对应的shm映射到进程中
核心Fuzz逻辑
解析参数,分析in目录的种子
perform_dry_run
1 |
|
AFL会维护一个当前的所有种子,保存在队列Queue_paths,dry_run会遍历整个种子队列,都运行一遍看看其是否能正常使用,不会有crash、timeout这种,dry到哪了会打印出来
1 | u8 *fn = strrchr(q->fname, '/') + 1; |
1 |
|
具体dry是通过calibrate_case函数,这里面会运行多次,以确保是样本是没问题的,默认就是8次
1 | //calibrate_case 8次,确保输入是没问题的 |
AFL++每次进行fuzz前都会先对样本queue进行依次dry(就上面分析的)
这个正常是没问题的,但是因为我们针对glib老是崩溃的问题,用了fuzz启动器不停的启动fuzz,这就导致,后期resume模式下,queue越来越长,这里dry的时间就会越来长,一种解决方式是可以减少stage_max到1次,而且按AFL,也必须运行一次,因为要载入trace_bits…
保存状态
和Fpicker的通讯
2-16
Libdislocator
Fpicker
fpicker: Fuzzing with Frida – Insinuator.net
最初的介绍文档
fpicker的Android移植
虽然fpicker最初写的支持Android、Linux、Macos,但是其实对Android的支持非常有限
从源代码里可以看到用USB时,不支持共享内存,也不支持AFL
直接搜Android是能找到很多和我一样的疑问的,如何使fpicker运行在Android上
讨论的非常多
marcinguy/fpicker at android-port-forward (github.com)
有许多讨论,不过作者都没有合并,这个是让fpicker支持网络模式的
Fuzzing in afl++ mode on android device · Issue #5 · ttdennis/fpicker (github.com)
这个是讨论怎么支持afl++在Android模式上
作者说因为fpicker需要AFL++的共享内存,如果要跨USB使用,就得用standalone mode,然后自己实现一个更好的mutator,否则就得用fpicker自己实现的random变异
因为Android上没有全局共享内存的概念,
当时本来觉得研究就此无望了,结果这老哥隔了一年,就在我发现这个玩意的上个月把自己的fpicker移植Android的成果发出来了
https://github.com/marcinguy/fpicker-aflpp-android/
其瑕疵还是不少,但是就此开启了我后面两个月的debug。。。。
之所以作者能把fpicker移植成功,是因为其将共享内存的相关代码都换成了android-shmem的模拟实现上
源码分析
fpicker一共就三个文件,fpicker.c 、fp_communicate.c 、fp_afl_mode.c
fpicker.c是负责fpicker的启动和一些通用类
fp_communicate.c是负责和harness Agent的交流
fp_afl_mode.c是负责和AFL交流
fpicker的main主要下面几个过程
解析参数
1 | fuzzer_state_t *fstate = parse_args(argc, argv); |
fstate是包含整个fstate状态的结构体,其中fconfig记录了当前解析参数得到各项配置
具体一些选项可以看github
https://github.com/ttdennis/fpicker
我们主要用的几个选项就是afl,in_process、spawn,一个样例启动参数如下
1 | AFL_DEBUG=1 AFL_SKIP_BIN_CHECK=1 LD_PRELOAD=/data/local/tmp/libandroid-shmem.so AFL_NO_AFFINITY=1 ./afl-fuzz -i- -o out -- ./fpicker -v -u send --fuzzer-mode afl -e spawn -p com.zzr.testfuzz -f ./agent.js -t 9999 com.zzr.testfuzz |
配置和AFL进行覆盖率通信的SHM
1 | if (fstate->config->fuzzer_mode == FUZZER_MODE_AFL) { |
这个shm会向下传递给stalker,其会在执行到基本块时写进这个共享内存
配置和Harness交流shm
1 | fstate->config->verbose = true; |
不知道为什么在fpicker中没办法创建共享内存,因为LD_PRELOAD=/data/local/tmp/libandroid-shmem.so打在AFL的启动前面的,可能没办法hook到fpicker这个子进程上,所以我采用的办法和覆盖率SHM时一样的,就是由AFL创建,然后fpicker再打开使用
1 |
|
因为Android不支持信号量,所以exec_sem和iteration_sem两个信号量也是用共享内存实现的,一开始担心不是原子操作会不会出同步问题,后来想想,两个共享内存负责进出还好,不会出现问题。。
frida加载
首先是frida初始化
1 | frida_init() |
这个就是直接调用core里面的API,说起来frida-core下载解压后,其API都在frida-core.h里面
创建设备管理器,遍历设备,得到当前的设备数目
1 | manager = frida_device_manager_new(); |
经过测试,只有用Remote类型连上frida-server才能成功进行接下来的操作,
得到device需要启动并附加到device上的进程中,因为我们要考虑到fuzz过程中crash、以及目标app突然退出的问题,所以必须采用spawn模式
1 | FridaSession *spawn_or_attach(fuzzer_state_t *fstate) { |
目前版本的frida,spawn启动,只要process_name是app的包名就行,其会自动启动app,其实当时我还想过一个折衷方案,就是写一个c程序用pm去启动,再用attach去附加,不过后来摸索出来直接用frida去spawn的API写法,这里spawn完成会返回pid
1 | session = frida_device_attach_sync(device, target_pid, FRIDA_REALM_NATIVE, NULL, &error); |
紧接着调用attach把frida挂上去,得到session,最后还有两个关键的步骤,因为frida spawn之后,程序是卡在入口的,所以这里要resume,让其恢复运行
1 | frida_device_resume_sync(fstate->frida_device,fstate->target_pid,NULL,&error); |
编译
Harness Agent
Prepare阶段
有时候fuzz的不是静态方法,就需要在prepare阶段找一下对象实例
1 | fuzz(payload, len) { |
1 | Java.perform(function () { |
数组类型
用frida的API,Java.array,这个效率奇低,不知道为什么,因为经过了Java虚拟机吗。。
但是如果我们想fuzz的就是Java层的API,那就只能用这种方式,除非再往下逆向到JNI层
Scudo
新版Android的Scudo确实给我带来了许多麻烦。。
内存标签
Andriod Native | 采样型内存调试工具GWP-ASan - 掘金 (juejin.cn)
Scudo分配的内存会在高位上打一个tag,所以后面我们做malloc hook的时候,就要判断下这个tag,
Scudo crash流程分析
2-27
Fuzz测试
testfuzz
自己写的测试程序,写了几个简单的crash的条件分支
访问0地址和堆溢出
要注意堆溢出并不一定会立马产生crash,特别是如果没有加ASAN的情况下,因此在我们这个测试场景下,延迟的crash只是因为堆的overflow不能立马造成crash,后面一系列继续的堆操作才导致的crash,如果这个继续的堆操作刚好位于下次的执行就会出现延迟crash的问题,
因为我们还没研究重置堆环境这个问题。
所以这个fuzz,不算太精准的fuzz
Fuzz启动器
其实可能也是上面介绍的堆的问题,fuzz很不稳定,大概fuzz到一定次数就会报一个glib的错误,至今还没解决,所以我只能采取一个替代方案,就是写一个fuzz启动器,监测到fuzz结束就重启,
主循环如下
1 | int main() { |
其实可以看作是一个daemon进程,只不过第一次启动时参数用的是正常的”-i”,”in”
第二次启动时用的参数是”-i-“
这里每次一轮fuzz结束时,会找一下crash的原因,如果是g_array_append_vals,就说明是glib崩溃,否则说明确实找到了crash
这里用到的两个子函数,负责删除每一轮产生的fuzz.log以及在fuzz.log中查找字符串
1 | int deleteFuzzlog(){ |
这里还有个小细节
这个first_iter=0,不能写在子进程中,这样设置,不会同步到父进程里,所以下次first_iter还是等于1
屏幕唤醒器
。。本来afl在后台跑应该是支持息屏的。。但是息屏app好像就不跑了,导致fuzz这边也会卡顿,因此又写了一个息屏唤醒器,每5分钟唤醒一下,因为三星手机没办法设置一直保持唤醒,所以只要这里唤醒的间隔大于,手机最大设置的唤醒间隔就行了
1 | #include <stdio.h> |
用的是adb命令,,这里要发两次keyevent,一次是息屏,第二次是开屏
libpng
自己的testFuzz基本算是测试完成了,找了个libpng测试一下
julienr/libpng-android at stable (github.com)
直接把文件全拉过来,只不过这里编译需要zlib,不过Android NDK有zlib,find然后targetlink就行
测试函数
1 | Java_com_zzr_testfuzz_MainActivity_testlibpng(JNIEnv *env, jclass clazz) { |
其输入是读取的“/data/local/tmp/test_libpng.png”
所以frida脚本fuzzInternal里传递输入要写入到这个文件里
这里加上libpng的打印了,所以如果获取成功,这里是可以看到打印的